package com.mlh.myspring.ioc;

import com.mlh.myspring.annotation.Autowired;
import com.mlh.myspring.annotation.Component;
import com.mlh.myspring.annotation.ComponentScan;
import com.mlh.myspring.annotation.Scope;
import com.mlh.myspring.processor.BeanPostProcessor;
import com.mlh.myspring.processor.InitializingBean;
import org.apache.commons.lang.StringUtils;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

//充当容器类,类似 Spring 原生的 ApplicationContext
public class MlhSpringApplicationContext {
    //配置文件class对象
    private Class config;
    //定义属性BeanDefinitionMap 用于存放BeanDefinition 对象
    private ConcurrentHashMap<String,BeanDefinition>beanDefinitionMap=new ConcurrentHashMap<>();
    //定义属性singletonObjects 用于存放单例对象
    private ConcurrentHashMap<String,Object>singletonObjects=new ConcurrentHashMap<>();
    //定义一个属性ArrayList, =>存放后置处理器（在实际开发中会有多个后置处理器）
    private List<BeanPostProcessor>beanPostProcessorList=new ArrayList<>();

    //构造器
    public MlhSpringApplicationContext(Class config){
        this.config=config;
        //该方法完成对指定包的扫描，并将Bean信息封装到BeanDefinition对象中，再放入到Map中
        beanDefinitionsByScan(config);
        //单例池初始化方法
        createSingleton();
    }

    //单例池初始化方法
    private void createSingleton(){
        //通过beanDefinitionMap来初始化singletonObjects单例池
        //遍历所有的beanDefinitino对象
        Enumeration<String> keys = beanDefinitionMap.keys();
        while (keys.hasMoreElements()){//遍历beanDefinitionMap中所有的bean
            String beanName=keys.nextElement();
            //通过beanName获取到对应的beanDefinition对象
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            //判断该bean是singleton还是prototype
            String scope = beanDefinition.getScope();
            if("singleton".equalsIgnoreCase(scope)){//equalsIgnoreCase方法 不会判断大小写区别
                //如果是单例则创建bean 并且放入单例池
                Object bean = createBean(beanDefinition,beanName);
                singletonObjects.put(beanName,bean);
            }
        }
    }

    //该方法完成对指定包的扫描，并将Bean信息封装到BeanDefinition对象中，再放入到Map中
    public void beanDefinitionsByScan(Class config){
        //1. 解析配置类
        //2. 获取到配置类的注解 @ComponentScan("com.mlh.spring.component")
        ComponentScan componentScan =(ComponentScan)this.config.getDeclaredAnnotation(ComponentScan.class);
        String path=componentScan.value();
        //3. 获取扫描路径下所有的类文件
        //(1) 先得到类加载器, 使用 App 方式来加载
        ClassLoader classLoader = MlhSpringApplicationContext.class.getClassLoader();
        //在获取某个包的对应的 URL 时，要求是 com/hspedu/spring/component
        //URL resource = classLoader.getResource("com/hspedu/spring/component");
        //(2) 将 path 转成 形式为 com/hspedu/spring/component
        path=path.replace(".","/");//path-->com/mlh/myspring/component
        //(3) 这里是获取到我们要加载的资源(类)的路径(工作路径 :
        //file:/D:/javaProject/spring/target/classes/com/mlh/myspring/component
        //获得工作路径的目的是为了扫描该路径下的类(为什么不扫描源文件呢？我是这样理解的：因为工作路径中存在，项目中才是真的可以创建)
        URL resource=classLoader.getResource(path);//resource-->file:/D:/javaProject/spring/target/classes/com/mlh/myspring/component
        System.out.println(resource);
        File file = new File(resource.getFile());
        if(file.isDirectory()){
            for (File f : file.listFiles()) {
                String fileAbsolutePath=f.getAbsolutePath();
                if(fileAbsolutePath.endsWith(".class")){//说明是类文件
                    //通过类加载器获取来类文件的 Clazz 对象
                    // 先得到类的完整类路径形式为com.hspedu.spring.component.MonsterService
                    //获得类名
                    String className=fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1,
                            fileAbsolutePath.indexOf(".class"));
                    String classFullPath = path.replace("/", ".") + "." + className;
                    try {
                        //获取到扫描包下的类的 clazz 对象
                        Class<?> clazz = classLoader.loadClass(classFullPath);
                        if (clazz.isAnnotationPresent(Component.class)) {//如果这个类有@Commponent, 说明是一个bean
                            //System.out.println("这是一个bean"+clazz +"类名="+className);

                            //1.为了方便，老韩这里将后置处理器放入到一个ArrayList
                            //2.如果发现是一个后置处理器，放入到beanPostProcessorList
                            //3.在原生的Spring容器中，对后置处理器还是走的getBean，createBean
                            //，但是需要我们在singletonObjects加入相应的业务逻辑
                            //4.因为这里我们是为了讲解后置处理去的机制，我就简化
                            //5.如果小伙伴们，仍然走以前的逻辑，也可以，就是要麻烦一点

                            //判断当前的这个clazz有没有实现BeanPostProcessor（通过实现接口来判断）
                            //说明，这里我们不能使用instanceof来判断clazz是否实现BeanPostProcessor
                            //原因: clazz 不是一个实例对象，而是一个类对象，使用isAssignableFrom
                            if(BeanPostProcessor.class.isAssignableFrom(clazz)){
                                BeanPostProcessor beanPostProcessor = (BeanPostProcessor)clazz.newInstance();
                                //放入到beanPostProcessorList中
                                beanPostProcessorList.add(beanPostProcessor);
                                //这个continue是为了不用再次去生成后置处理器bean了  因为在beanPostProcessorList中已经生成过了
                                continue;
                            }

                            //先获取beanName
                            Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);
                            String beanName=componentAnnotation.value();
                            if("".equals(beanName)){
                                //如果beanName是空串则利用类名首字母小写作为beanName
                                beanName= StringUtils.uncapitalize(className);
                            }
                            //将Bean的信息封装到BeanDefinition对象中 -->放入到BeanDefinitionMap对象中
                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setClazz(clazz);
                            //还有个scope的信息要去获取
                            if (clazz.isAnnotationPresent(Scope.class)){
                                Scope scopeAnnotation = clazz.getDeclaredAnnotation(Scope.class);
                                beanDefinition.setScope(scopeAnnotation.value());
                            }else{
                                //没有scope注解则默认是singleton类型
                                beanDefinition.setScope("singleton");
                            }
                            //将beanDefinition对象放入到Map中
                            beanDefinitionMap.put(beanName,beanDefinition);
                        }else{
                            System.out.println("不是一个 bean = " + clazz+"类名="+className);
                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    } catch (InstantiationException e) {
                        throw new RuntimeException(e);
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }

                }
            }

        }
    }

    //完成createBean(BeanDefinition,beanName)方法
    //通过BeanDefinition对象来创建bean实例
    private Object createBean(BeanDefinition beanDefinition,String beanName){
        Class clazz = beanDefinition.getClazz();
        try {
            //使用反射得到实例
            Object instance = clazz.getConstructor().newInstance();
            //遍历要创建对象的所有属性 查看那些属性需要依赖注入
            for(Field declaredField:clazz.getDeclaredFields()){
                //查看那些属性有@Autowired
                if(declaredField.isAnnotationPresent(Autowired.class)){
                    //获取该属性的属性名
                    String name = declaredField.getName();
                    //通过getBean方法获取bean，并将其注入
                    //依赖注入这边忽略了一点细节  如果被注入的是单例，而这个单例还没被创建好实例  这个getBean肯定是返回null
                    //因为单例池的创建也是通过createBean来实现的，因此如果真要实现该功能必须要注意实例创建的先后关系
                    Object bean = getBean(name);
                    declaredField.setAccessible(true);//由于是私有属性，需要先进行暴破
                    declaredField.set(instance,bean);//依赖注入
                }
            }
            //--------------------------------------------------------------------------------------
            //这里值得注意的地方是 ioc容器中所有的bean都需要通过createBean来创建，因此所有的bean的创建都会去调用后置处理器的方法
            //这样的效果就是切面编程  很有意思
            //我们在Bean的初始化方法前，调用后置处理器的before方法
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                //在后置处理器的before方法，可以对容器的bean实例进行处理
                //然后返回处理后的bean实例
                Object current = beanPostProcessor.postProcessBeforeInitialization(instance, beanName);
                //保证postProcessBeforeInitialization()方法可以返回为null
                if(current!=null){
                    instance=current;
                }
            }
            //--------------------------------------------------------------------------------------
            //这里判断是否要执行Bean初始化方法（初始化不是实例化哦  不要误解！！！）
            //1.判断当前创建的Bean对象是否实现InitializingBean接口
            //2. instanceof java 基础中讲表判断某个对象的运行类型是不是某个类型或者某个类型的子类型
            //这里就使用到了接口编程（标记接口）
            if(instance instanceof InitializingBean){
                //将instance转成InitializingBean类型  因为这边的instance是object
                //调用初始化方法
               ((InitializingBean)instance).afterPropertiedSet();
            }
            //--------------------------------------------------------------------------------------
            //我们在Bean的初始化方法后，调用后置处理器的after方法
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                //在后置处理器的after方法，可以对容器的bean实例进行处理
                //然后返回处理后的bean实例
                Object current = beanPostProcessor.postProcessAfterInitialization(instance, beanName);
                //保证postProcessAfterInitialization()方法可以返回为null  原生的spring就是可以返回null的
                if(current!=null){
                    instance=current;
                }
            }
            return instance;
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    //实现ioc的 getbean()方法
    public Object getBean(String name){
        if(beanDefinitionMap.containsKey(name)){//判断传入的name是否有在beanDefinitionMap中
            BeanDefinition beanDefinition = beanDefinitionMap.get(name);
            //判断bean是singleton 还是 prototype
            String scope = beanDefinition.getScope();
            if("singleton".equalsIgnoreCase(scope)){
                //如果是singleton则直接从单例池中获取并返回
                return singletonObjects.get(name);
            }else{
                //如果是prototype 则调用createBean 创建对象并返回
                return createBean(beanDefinition,name);
            }
        }else{//如果不在 则抛出空指针异常
            throw new NullPointerException("没有该bean");
        }
    }
}
